package com.king.base.config.weChat;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @author Spur
 * @date 2019/8/20
 */
@Component
public class WeChatClient {
    private static Logger log = LoggerFactory.getLogger(WeChatClient.class);
    @Autowired
    private WeChatConfiguration config;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    /**
     * 通过CORPID、SECRET向微信开放平台请求access_token
     *
     * @return access_token
     */
    public String getAccessToken() {
        String accessToken = null;
        if (redisTemplate.opsForValue().get("ACCESS_TOKEN") != null) {
            accessToken = redisTemplate.opsForValue().get("ACCESS_TOKEN").toString();
        } else {
            //拼接请求access_token的链接
            String params = "corpid=" + config.getCorpId() + "&corpsecret=" + config.getSecret();
            String resultJson = sendGet(config.getOauthAccessTokenUrl(), params);
            //将返回的json数据转换为Map结构存储
            Map<String, Object> map = parseData(resultJson);
            accessToken = map.get("access_token").toString();
            //数字签名有效时间为2小时，不放入redis
            //redisTemplate.opsForValue().set("ACCESS_TOKEN", accessToken);

            //取JSAPI_TICKET
            getJsApiTicket(accessToken);
        }
        return accessToken;
    }

    /**
     * 函数名称: getUserinfo
     * 函数描述: 通过access_token去获取用户的信息
     *
     * @param code code
     * @return Map<String ,   String>
     */
    public Map<String, Object> getUserInfo(String code) {

        //access_token超时,此时需要重新获得一个access_token。
        StringBuffer paramsUserInfo = new StringBuffer();
        paramsUserInfo.append("access_token=").append(getAccessToken());
        paramsUserInfo.append("&code=").append(code);
        String resultJson = sendGet(config.getSnsUserInfoUrl(), paramsUserInfo.toString());
        //将返回的json数据转换为Map结构存储
        Map<String, Object> map = parseData(resultJson);
        if (!"0".equals(map.get("errcode").toString())) {
            log.error("********** Get User Info **********");
            log.error("code:" + code);
            log.error("return:" + JSON.toJSONString(map));
            log.error("********** Get User Info **********");
        }
        //access_token超时,需要重新获得access_token，再请求用户信息
        if ("42001".equals(map.get("errcode").toString())) {
            redisTemplate.delete("ACCESS_TOKEN");
            map = getUserInfo(code);
        }
        return map;
    }

    /**
     * 函数名称: getJsApiTicket
     * 函数描述: 通过access_token去获取JsApiTicket
     *
     * @param accessToken access_token
     * @return Map<String       ,               String>
     */
    public void getJsApiTicket(String accessToken) {
        //access_token超时,此时需要重新获得一个access_token。
        StringBuffer params = new StringBuffer();
        params.append("access_token=").append(accessToken);
        String resultJson = sendGet(config.getGetJsApiTicketUrl(), params.toString());
        //将返回的json数据转换为Map结构存储
        Map<String, Object> map = parseData(resultJson);
        if ("0".equals(map.get("errcode").toString())) {
            String jsapiTicket = map.get("ticket").toString();
            redisTemplate.opsForValue().set("JSAPI_TICKET", jsapiTicket);
        }
    }

    /**
     * 函数名称: parseData
     * 函数描述: 将json字符串转换为Map<String, String>结构
     *
     * @param data
     * @return Map<String       ,               String>
     */
    private static Map<String, Object> parseData(String data) {
        Map map = JSON.parseObject(data, Map.class);
        return map;
    }


    /**
     * 向指定URL发送GET方法的请求
     *
     * @param url   发送请求的URL
     * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();

            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常！" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    public Map<String, String> sign(String url) {
        Map<String, String> ret = new HashMap<String, String>();
        //这里的jsapi_ticket是获取的jsapi_ticket。
        getAccessToken();
        String jsapiTicket = redisTemplate.opsForValue().get("JSAPI_TICKET").toString();
        String nonceStr = createNonceStr();
        String timestamp = createTimestamp();
        String string1;
        String signature = "";

        //注意这里参数名必须全部小写，且必须有序
        string1 = "jsapi_ticket=" + jsapiTicket +
                "&noncestr=" + nonceStr +
                "&timestamp=" + timestamp +
                "&url=" + url;

        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapiTicket);
        ret.put("nonceStr", nonceStr);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);
        ret.put("appId",config.getCorpId());
        ret.put("agentid",config.getAgentId());
        return ret;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String createNonceStr() {
        return UUID.randomUUID().toString();
    }

    private static String createTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    /**
     * 发送https请求
     *
     * @param requestUrl    请求地址
     * @param requestMethod 请求方式（GET、POST）
     * @param outputStr     提交的数据
     * @return ReturnMsg(通过JSONObject.get ( key)的方式获取json对象的属性值)
     */
    public static ReturnMsg httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        ReturnMsg returnMsg = null;
        try {
            // 创建SSLContext对象，并使用我们指定的信任管理器初始化
            TrustManager[] tm = {new MyX509TrustManager()};
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 从上述SSLContext对象中得到SSLSocketFactory对象
            SSLSocketFactory ssf = sslContext.getSocketFactory();
            URL url = new URL(requestUrl);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式（GET/POST）
            conn.setRequestMethod(requestMethod);
            // 当outputStr不为null时向输出流写数据
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意编码格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }
            // 从输入流读取返回内容
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            // 释放资源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            conn.disconnect();
            returnMsg = JSONObject.parseObject(buffer.toString(), ReturnMsg.class);
        } catch (Exception e) {
        }
        return returnMsg;
    }

    /**
     * 企业应用给用户发送消息
     *
     * @param msgType  消息类型 text image voice video file textcard news mpnews
     * @param toUser   成员ID列表（消息接收者，多个接收者用‘|’分隔，最多支持1000个）。特殊情况：指定为@all，则向该企业应用的全部成员发送
     * @param text     文本消息内容
     * @param textcard 文本卡片消息内容
     * @return
     */
    public ReturnMsg sendMsg(String msgType, String toUser, Text text, TextCard textcard) {
        String outputStr = "";
        if ("text".equals(msgType)) {
            TextMsg textMsg = new TextMsg();
            textMsg.setAgentid(config.getAgentId());
            textMsg.setMsgtype(msgType);
            textMsg.setTouser(toUser);
            textMsg.setText(text);
            //需要提交的数据
            outputStr = JSON.toJSONString(textMsg);

        } else if ("textcard".equals(msgType)) {
            TextCardMsg textcardMsg = new TextCardMsg();
            textcardMsg.setAgentid(config.getAgentId());
            textcardMsg.setMsgtype(msgType);
            textcardMsg.setTouser(toUser);

            textcardMsg.setTextCard(textcard);
            //需要提交的数据
            outputStr = JSON.toJSONString(textcardMsg);
        }

        int errCode = 0;
        //拼接请求地址
        String requestUrl = config.getSendMsgUrl().replace("ACCESS_TOKEN", getAccessToken());
        //发送消息
        ReturnMsg returnMsg = httpsRequest(requestUrl, "POST", outputStr);
        if (0 != returnMsg.getErrcode()) {
            log.error("***************Weixin MSG****************");
            log.error("TOUSER:" + toUser);
            log.error("CONTENT:" + JSON.toJSONString(text != null ? text : textcard));
            log.error("RETURN:" + JSON.toJSONString(returnMsg));
            log.error("***************Weixin MSG****************");
        }
        // access_token超时,需要重新获得access_token，再发送消息
        if (42001 == returnMsg.getErrcode()) {
            redisTemplate.delete("ACCESS_TOKEN");
            returnMsg = sendMsg(msgType, toUser, text, textcard);
        }
        return returnMsg;
    }

}
